﻿Imports System.Text.RegularExpressions

''' <summary>
''' 親関連列属性
''' </summary>
''' <remarks>フレームワーク内で使用される属性のため、手動インスタンスは不可。</remarks>
Public Class ParentRelationColumnAttribute
    Inherits ColumnAttribute
    Implements IParentRelation

#Region "Constructor"
    Private Sub New()
    End Sub

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <param name="myProp"></param>
    ''' <param name="myColumnName"></param>
    ''' <param name="yourColumn"></param>
    ''' <param name="isNullable"></param>
    ''' <remarks>
    ''' 基底リレーション用として必要
    ''' </remarks>
    Public Sub New(myProp As Reflection.PropertyInfo, myColumnName As String, yourColumn As ColumnAttribute, isNullable As Boolean)
#If DEBUG Then
        If yourColumn Is Nothing Then Throw New ArgumentException("yourColumnは省略できません")
#End If

        Me.ColumnName = myColumnName

        'フレームワークからのインスタンスは許容します。
        Me.PropertyInfo = myProp

        _yourColumn = yourColumn
        _isNullable = isNullable
    End Sub

    ''' <summary>
    ''' 相手列から生成します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <param name="yourColumn"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' #101 生成されるSQL文にエスケープ文字を追加する
    ''' </remarks>
    Public Shared Function CreateInstanceFromColumn(prop As Reflection.PropertyInfo, yourColumn As ColumnAttribute) As ParentRelationColumnAttribute
        Dim yourType As Type = prop.PropertyType

        Dim c As New ParentRelationColumnAttribute
        c.ColumnName = ParentRelationColumnAttribute.CreateMyColumnName(prop, yourColumn)
        c.PropertyInfo = prop
        c._yourColumn = yourColumn
        c._isNullable = AttributeReader.Read(Of NullableAttribute)(prop).Any
        c.Key = KeyAttributeReader.Read(prop).FirstOrDefault

        Return c
    End Function

    ''' <summary>
    ''' リレーションから生成します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <param name="rel"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' #101 生成されるSQL文にエスケープ文字を追加する
    ''' </remarks>
    Public Shared Function CreateInstanceFromRelation(prop As Reflection.PropertyInfo, rel As RelationAttribute) As ParentRelationColumnAttribute
        Dim yourType As Type = prop.PropertyType

        Dim c As New ParentRelationColumnAttribute
        c._columnName = rel.MyColumnName
        c.PropertyInfo = prop
        c._yourColumn = (From el As ColumnAttribute In yourType.ToTable.Columns Where el.IsPrimaryKey AndAlso el.ColumnName = rel.YourColumnName).FirstOrDefault
        c._isNullable = AttributeReader.Read(Of NullableAttribute)(prop).Any
        c.Key = KeyAttributeReader.Read(prop).FirstOrDefault

        Return c
    End Function

    ''' <summary>
    ''' 親と結合できる列名を取得します
    ''' </summary>
    ''' <param name="prop">親を格納するプロパティ</param>
    ''' <param name="yourColumn">結合先の列（主キー）</param>
    ''' <returns></returns>
    ''' <remarks>
    ''' 「子（自分）」からみて「親（相手）」の結合先列名を取得するロジック。
    ''' なお取得した列名は「子（自分）」が保持することになります。
    ''' 
    ''' 規約と命名規則
    ''' .プロパティ名は親の型名と同じにすること。ただし、接頭語は認める。
    ''' .列名は親主キー列名と同じであると解釈される。ただし、例外があるので、優先順位順に記載する。
    ''' 　A.　プロパティに接頭語がある場合、列名は「接頭語」＋「親主キー列名」が適用される。
    ''' 　B.　親クラスが継承をしていない場合、列名は「親主キー列名」が適用される。
    ''' 　C.　親クラスが継承している場合、列名は「親クラス名」＋「親主キー列名」が適用される。（#66）
    ''' 　　　ただし、親クラスが接頭語形式で命名されている場合、列名は「親クラス接頭語」＋「親主キー列名」が適用される。（#80）
    ''' 
    ''' 注意事項
    ''' ・Nリレーション表現時、プロパティ名に接頭語を使用できません。（#85）
    ''' </remarks>
    Private Shared Function CreateMyColumnName(prop As Reflection.PropertyInfo, yourColumn As ColumnAttribute) As String
        Dim getPrefix As Func(Of String) =
            Function() As String
                'パターンA
                Dim m As Text.RegularExpressions.Match = Regex.Match(prop.Name, String.Format("(?<prefix>^.+){0}$", prop.PropertyType.Name))
                If m.Success Then Return m.Groups("prefix").Value

                '継承していない場合、パターンB（接頭語なし）
                If yourColumn.Table.IsRootBase Then Return String.Empty

                'パターンC例外
                m = Regex.Match(yourColumn.Table.ModelType.Name, String.Format("(?<prefix>^.+){0}$", yourColumn.Table.ModelType.BaseType.Name))
                If m.Success Then Return m.Groups("prefix").Value

                'パターンC
                Return yourColumn.Table.ModelType.Name
            End Function

        '#77　継承したクラスを親プロパティにするとOracle命名規則が適用されない不具合の修正
        '#97　MySQL対応
        '#101 生成されるSQL文にエスケープ文字を追加する
#If DEBUG Then
        If String.IsNullOrEmpty(yourColumn.PlainColumnName) Then
            Throw New InvalidOperationException
        End If
#End If
        Dim name As String = StringFormConverter.Instance.ToPrefix(getPrefix.Invoke) & yourColumn.PlainColumnName

        Return name
    End Function
#End Region

#Region "BasicInfo"
    Public ReadOnly Property MyColumnName As String Implements IParentRelation.MyColumnName
        Get
            Return Me.ColumnName
        End Get
    End Property

    Private _yourColumn As ColumnAttribute
    Public ReadOnly Property YourColumn As ColumnAttribute Implements IParentRelation.YourColumn
        Get
            Return _yourColumn
        End Get
    End Property

    Public ReadOnly Property YourColumnName As String Implements IParentRelation.YourColumnName
        Get
            Return Me.YourColumn.ColumnName
        End Get
    End Property

    Private _isNullable As Boolean
    ''' <summary>
    ''' Nullを許容するかどうか示します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>コンストラクタで初期化されます</remarks>
    Public Overrides ReadOnly Property IsNullable As Boolean Implements IParentRelation.IsNullable
        Get
            Return _isNullable
        End Get
    End Property

    ''' <summary>
    ''' 親関連属性かどうかを示します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides ReadOnly Property IsParent As Boolean
        Get
            Return True
        End Get
    End Property

    Public ReadOnly Property IsSuperRelation As Boolean Implements IParentRelation.IsSuperRelation
        Get
            Return False
        End Get
    End Property
#End Region

#Region "GetValue SetValue"
    ''' <summary>
    ''' 値を取得します
    ''' </summary>
    ''' <param name="model"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides Function GetValue(model As Object) As Object
        Dim m As Object = Me.PropertyInfo.GetValue(model, Nothing)
        Dim val As Object = If(m Is Nothing, Nothing, Me.YourColumn.GetValue(m))
        Return val
    End Function

    ''' <summary>
    ''' 値を代入します
    ''' </summary>
    ''' <param name="myInstance"></param>
    ''' <param name="val"></param>
    ''' <remarks></remarks>
    Public Overrides Sub SetValue(myInstance As Object, val As Object)
        'valがNULLの場合、何もしません
        If IsDBNull(val) OrElse val.ToString = String.Empty Then
            '結合条件がない場合、存在しないとします。
            Return
        End If

        Dim prop As Reflection.PropertyInfo = Me.PropertyInfo
        Dim parent As Object = prop.GetValue(myInstance, Nothing)
        If parent Is Nothing Then
            '既存アイテムが存在しない場合、インスタンス
            parent = prop.PropertyType.GetConstructor(Type.EmptyTypes).Invoke(Nothing)
            prop.SetValue(myInstance, parent, Nothing)
        End If

        '#103 親－子－孫の関係時、子、孫を起点とした読み込みができない
        '結合先に代入処理を移譲する
        Me.YourColumn.SetValue(parent, val)
    End Sub
#End Region

#Region "Relation"
    ''' <summary>
    ''' 2つのインスタンスが結合できるかどうかを示します
    ''' </summary>
    ''' <param name="parentIncetance"></param>
    ''' <param name="childIncetance"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CanJoin(ByVal childIncetance As Object, ByVal parentIncetance As Object, Optional parentPropertyName As String = "") As Boolean
        If parentIncetance Is Nothing OrElse childIncetance Is Nothing Then Return False

        Dim myVal As Object = Me.YourColumn.GetValue(parentIncetance)

        Dim p As Reflection.PropertyInfo = PropertyReader.ReadParentPropertyInfo(childIncetance.GetType, parentIncetance.GetType, parentPropertyName)
        Dim parent As Object = p.GetValue(childIncetance, Nothing)

        '親が存在しない場合、結合はできません
        If parent Is Nothing Then Return False

        Dim yourVal As Object = Me.YourColumn.GetValue(parent)

        If Comparer.Equals(myVal, yourVal) Then
            Return True
        Else
            Return False
        End If
    End Function

    Public Sub Join(myInstance As Object, yourInstance As Object) Implements IParentRelation.Join
        Me.PropertyInfo.SetValue(myInstance, yourInstance, Nothing)
    End Sub
#End Region

    ''' <summary>
    ''' リレーション元列属性を取得します
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' #103 「親－子－孫の関係時、子、孫を起点とした読み込みができない」不具合修正
    ''' DLL時、実際に格納型を取得する際に利用
    ''' </remarks>
    Public Overrides Function GetRelationRootColumn() As ColumnAttribute
        If Me.YourColumn.IsParent Then
            Return DirectCast(Me.YourColumn, ParentRelationColumnAttribute).GetRelationRootColumn
        Else
            Return Me.YourColumn
        End If
    End Function

    Public Overrides Function ToString() As String
        Dim colName As String = String.Format("{0}.{1}", If(Me.Table Is Nothing, "unknown", Me.Table.TableName), Me.ColumnName)
        Dim propName As String = If(Me.PropertyInfo Is Nothing, "unknown", String.Format("{0}.{1}", Me.PropertyInfo.DeclaringType.Name, Me.PropertyInfo.Name))
        propName = String.Format("{0}.{1}", propName, If(Me.YourColumn Is Nothing, "unknown", Me.YourColumn.PropertyInfo.Name))

        Dim keyName As String = If(Me.IsLoadKey, "*", String.Empty)

        Return String.Format("{0}{1} ({2})", keyName, colName, propName)
    End Function

End Class
